﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using StyleCop.CSharp;

namespace Jsl.StyleCop.CSharp
{
    [TestClass]
    public class TestAstHelper
    {
        [TestMethod]
        public void Can_detect_true_and_false_boolean_constants()
        {
            var relation = (RelationalExpression)MethodCollectorRule.GetExpression("true == false");

            AssertEx.That(() => relation.LeftHandSide.IsBooleanConstant());
            AssertEx.That(() => relation.LeftHandSide.IsBooleanTrueConstant());
            AssertEx.That(() => relation.RightHandSide.IsBooleanConstant());
            AssertEx.That(() => relation.RightHandSide.IsBooleanFalseConstant());
        }

        [TestMethod]
        public void Can_detect_literals()
        {
            var literal = (LiteralExpression)MethodCollectorRule.GetExpression("x");

            AssertEx.That(() => literal.IsLiteral("x"));
        }

        [TestMethod]
        public void Can_detect_literals_negative_cases()
        {
            var literal = (LiteralExpression)MethodCollectorRule.GetExpression("x");

            AssertEx.That(() => !literal.IsLiteral("NOT"));
        }
    }

    [TestClass]
    public class When_examining_method_parameters
    {
        [TestMethod]
        public void Can_get_parameter_from_a_variable()
        {
            var method = MethodCollectorRule.MethodFromMethodCode(@"
public void Method(out string x)
{
    x = ""xxx"";
}");
            var variableToLookAt = method.Variables["x"];
            var parameter = method.GetParameterFromVariable(variableToLookAt);

            AssertEx.That(() => parameter.Name == "x");
        }

        [TestMethod]
        public void Can_get_parameter_from_a_literal_variable()
        {
            var method = MethodCollectorRule.MethodFromMethodCode(@"
public void Method(out string @x)
{
    @x = ""xxx"";
}");
            var variableToLookAt = method.Variables["x"];
            var parameter = method.GetParameterFromVariable(variableToLookAt);

            AssertEx.That(() => parameter.Name == "x");
        }
    }

    [TestClass]
    public class TestEvaluateExpression
    {
        [TestMethod]
        public void True_and_false_constants_are_evaluated()
        {
            new Helper("true")
                .Is(true);

            new Helper("false")
                .Is(false);
        }

        [TestMethod]
        public void If_no_expression_matches_then_Unknown_is_returned()
        {
            new Helper("x == 5")
                .Evaluator("BOB", true)
                .Is(null);
        }

        [TestMethod]
        public void If_single_expression_are_evaluated()
        {
            new Helper("x == 5")
                .Evaluator("x == 5", true)
                .Is(true)
                .Evaluator("x == 5", false)
                .Is(false)
                .Evaluator("x == 5", null)
                .Is(null);
        }

        [TestMethod]
        public void Parenthesis_are_removed_for_simplification()
        {
            new Helper("(x)")
                .Evaluator("x", true)
                .Is(true);
        }

        [TestMethod]
        public void Negation_is_removed()
        {
            new Helper("!x")
                .Evaluator("x", true)
                .Is(false)
                .Evaluator("x", false)
                .Is(true)
                .Evaluator("x", null)
                .Is(null);
        }

        [TestMethod]
        public void Or_conditions_are_handled()
        {
            // First part is true.
            var helper = new Helper("x == 5 || y == 6")
                .Evaluator("x == 5", true)
                .Evaluator("y == 6", true)
                .Is(true)
                .Evaluator("x == 5", true)
                .Evaluator("y == 6", false)
                .Is(true)
                .Evaluator("x == 5", true)
                .Is(true);

            // First part is false.
            helper.Evaluator("x == 5", false)
                .Evaluator("y == 6", true)
                .Is(true)
                .Evaluator("x == 5", false)
                .Evaluator("y == 6", false)
                .Is(false)
                .Evaluator("x == 5", false)
                .Is(null);

            // First part is unknown.
            helper.Evaluator("y == 6", true)
                .Is(true)
                .Evaluator("y == 6", false)
                .Is(null)
                // no matches
                .Is(null);
        }

        [TestMethod]
        public void And_conditions_are_handled()
        {
            // First part is true.
            var helper = new Helper("x == 5 && y == 6")
                .Evaluator("x == 5", true)
                .Evaluator("y == 6", true)
                .Is(true)
                .Evaluator("x == 5", true)
                .Evaluator("y == 6", false)
                .Is(false)
                .Evaluator("x == 5", true)
                .Is(null);

            // First part is false.
            helper.Evaluator("x == 5", false)
                .Evaluator("y == 6", true)
                .Is(false)
                .Evaluator("x == 5", false)
                .Evaluator("y == 6", false)
                .Is(false)
                .Evaluator("x == 5", false)
                .Is(false);

            // First part is unknown.
            helper.Evaluator("y == 6", true)
                .Is(null)
                .Evaluator("y == 6", false)
                .Is(false)
                // no matches
                .Is(null);
        }

        [TestMethod]
        public void Comparing_to_true_constant_is_handled()
        {
            new Helper("x == true")
                .Evaluator("x", true)
                .Is(true)
                .Evaluator("x", false)
                .Is(false)
                .Evaluator("x", null)
                .Is(null);

            new Helper("true == x")
                .Evaluator("x", true)
                .Is(true)
                .Evaluator("x", false)
                .Is(false)
                .Evaluator("x", null)
                .Is(null);

            new Helper("x != true")
                .Evaluator("x", false)
                .Is(true)
                .Evaluator("x", true)
                .Is(false)
                .Evaluator("x", null)
                .Is(null);

            new Helper("true != x")
                .Evaluator("x", false)
                .Is(true)
                .Evaluator("x", true)
                .Is(false)
                .Evaluator("x", null)
                .Is(null);
        }

        [TestMethod]
        public void Comparing_to_false_constant_is_handled()
        {
            new Helper("x == false")
                .Evaluator("x", false)
                .Is(true)
                .Evaluator("x", true)
                .Is(false)
                .Evaluator("x", null)
                .Is(null);

            new Helper("false == x")
                .Evaluator("x", false)
                .Is(true)
                .Evaluator("x", true)
                .Is(false)
                .Evaluator("x", null)
                .Is(null);

            new Helper("x != false")
                .Evaluator("x", true)
                .Is(true)
                .Evaluator("x", false)
                .Is(false)
                .Evaluator("x", null)
                .Is(null);

            new Helper("false != x")
                .Evaluator("x", true)
                .Is(true)
                .Evaluator("x", false)
                .Is(false)
                .Evaluator("x", null)
                .Is(null);
        }

        #region Private Classes
        [DebuggerStepThrough]
        private class Helper
        {
            private readonly IList<Func<Expression, bool?>> evaluators = new List<Func<Expression, bool?>>();
            private readonly Expression expression;

            #region Constructors
            public Helper(string expression)
            {
                this.expression = MethodCollectorRule.GetExpression(expression);
            }
            #endregion Constructors

            #region Public Methods
            public Helper Evaluator(string code, bool? result)
            {
                this.evaluators.Add(
                    expression => (code == expression.Text) ? result : null);

                return this;
            }

            public Helper Is(bool? expectedResult)
            {
                var result = this.expression.EvaluateExpression(this.evaluators);

                // Clear out the evaluators so this same helper class can be used again.
                this.evaluators.Clear();

                AssertEx.That(() => result == expectedResult);

                return this;
            }
            #endregion Public Methods
        }
        #endregion Private Classes
    }

    [TestClass]
    public class TestTryEvaluateIf
    {
        [TestMethod]
        public void Simple_if_works()
        {
            var helper = new Helper("if (C1) S1();");
            helper.Evaluator("C1", true)
                .Is(statement => AssertEx.That(() => statement.Tokens.MatchTokens("S1")));
            helper.Evaluator("C1", false)
                .Is(statement => AssertEx.That(() => statement == null));
            helper.Evaluator("C1", null)
                .Unknown();

            helper = new Helper("if (C1) { S1(); }");
            helper.Evaluator("C1", true)
                .Is(statement => AssertEx.That(() => statement.Tokens.MatchTokens("S1")));
            helper.Evaluator("C1", false)
                .Is(statement => AssertEx.That(() => statement == null));
            helper.Evaluator("C1", null)
                .Unknown();
        }

        [TestMethod]
        public void Simple_if_else_works()
        {
            var helper = new Helper("if (C1) S1(); else S2();");
            helper.Evaluator("C1", true)
                .Is(statement => AssertEx.That(() => statement.Tokens.MatchTokens("S1")));
            helper.Evaluator("C1", false)
                .Is(statement => AssertEx.That(() => statement.Tokens.MatchTokens("S2")));
            helper.Evaluator("C1", null)
                .Unknown();

            helper = new Helper("if (C1) { S1(); } else { S2(); }");
            helper.Evaluator("C1", true)
                .Is(statement => AssertEx.That(() => statement.Tokens.MatchTokens("S1")));
            helper.Evaluator("C1", false)
                .Is(statement => AssertEx.That(() => statement.Tokens.MatchTokens("S2")));
            helper.Evaluator("C1", null)
                .Unknown();
        }

        [TestMethod]
        public void Complex_if_inside_if_works()
        {
            var helper = new Helper(@"
                if (C1)
                {
                    if (C2)
                    {
                        S1();
                    }
                    else
                    {
                        S2();
                    }
                }
                else
                {
                    if (C3)
                    {
                        S3();
                    }
                    else
                    {
                        S4();
                    }
                }");

            // Validate C1 not specified
            helper.Evaluator("C1", null)
                .Unknown();

            // Validate the C1 == true side.
            helper.Evaluator("C1", true)
                .Is<IfStatement>(statement => AssertEx.That(() => statement.ConditionExpression.Text == "C2"));
            helper.Evaluator("C1", true)
                .Evaluator("C2", true)
                .Is(statement => AssertEx.That(() => statement.Tokens.MatchTokens("S1")));
            helper.Evaluator("C1", true)
                .Evaluator("C2", false)
                .Is(statement => AssertEx.That(() => statement.Tokens.MatchTokens("S2")));
            helper.Evaluator("C1", true)
                .Evaluator("C2", null)
                .Is<IfStatement>(statement => AssertEx.That(() => statement.ConditionExpression.Text == "C2"));

            // Validate the C1 == false side.
            helper.Evaluator("C1", false)
                .Is<IfStatement>(statement => AssertEx.That(() => statement.ConditionExpression.Text == "C3"));
            helper.Evaluator("C1", false)
                .Evaluator("C3", true)
                .Is(statement => AssertEx.That(() => statement.Tokens.MatchTokens("S3")));
            helper.Evaluator("C1", false)
                .Evaluator("C3", false)
                .Is(statement => AssertEx.That(() => statement.Tokens.MatchTokens("S4")));
            helper.Evaluator("C1", false)
                .Evaluator("C3", null)
                .Is<IfStatement>(statement => AssertEx.That(() => statement.ConditionExpression.Text == "C3"));
        }

        #region Private Classes
        [DebuggerStepThrough]
        private class Helper
        {
            private readonly IList<Func<Expression, bool?>> evaluators = new List<Func<Expression, bool?>>();
            private readonly IfStatement ifStatement;

            #region Constructors
            public Helper(string ifCode)
            {
                this.ifStatement = MethodCollectorRule.GetIfStatement(ifCode);
            }
            #endregion Constructors

            #region Public Methods
            public Helper Evaluator(string code, bool? result)
            {
                this.evaluators.Add(
                    expression => (code == expression.Text) ? result : null);

                return this;
            }

            public Helper Unknown()
            {
                Statement statement;
                AssertEx.That(() => !this.ifStatement.TryEvaluateIf(out statement, this.evaluators));

                return this;
            }

            public Helper Is(Action<Statement> test = null)
            {
                return this.Is<Statement>(test);
            }

            public Helper Is<TStatement>(Action<TStatement> test = null)
                where TStatement : class
            {
                Statement statement;
                var result = this.ifStatement.TryEvaluateIf(out statement, this.evaluators);

                // Clear out the evaluators so this same helper class can be used again.
                this.evaluators.Clear();

                TStatement typedStatement = statement as TStatement;
                if (statement != null)
                {
                    AssertEx.That(() => typedStatement != null);
                }

                AssertEx.That(() => result);

                if (test != null)
                {
                    test(typedStatement);
                }

                return this;
            }
            #endregion Public Methods
        }
        #endregion Private Classes
    }

    [TestClass]
    public class TestEvaluateNullCheckExpression
    {
        [TestMethod]
        public void TestCases()
        {
            ListBuilder<string, string, string, bool?>
                // Standard comparisons.
                .AddItem("x == null", "x", null, true)
                .AddItem("x == null", null, "x", false)
                .AddItem("x == null", null, null, null)
                // Backwards comparisons.
                .AddItem("null == x", "x", null, true)
                .AddItem("null == x", null, "x", false)
                .AddItem("null == x", null, null, null)
                // Standard comparisons.
                .AddItem("x != null", "x", null, false)
                .AddItem("x != null", null, "x", true)
                .AddItem("x != null", null, null, null)
                // Backwards comparisons.
                .AddItem("null != x", "x", null, false)
                .AddItem("null != x", null, "x", true)
                .AddItem("null != x", null, null, null)
                // Invalid comparisons.
                .AddItem("x > null", "x", null, null)
                .AddItem("x == y", "x", null, null)
                .Run(Test);
        }

        #region Private Methods
        private static void Test(
            string codeExpression,
            string trueCode,
            string falseCode,
            bool? expectedResult)
        {
            var result = MethodCollectorRule
                .GetExpression(codeExpression)
                .EvaluateNullCheckExpression(
                    expression =>
                    {
                        if (expression.Text == trueCode)
                        {
                            return true;
                        }
                        else if (expression.Text == falseCode)
                        {
                            return false;
                        }

                        return null;
                    });

            AssertEx.That(() => result == expectedResult);
        }
        #endregion Private Methods
    }
}